Icon Services In Apps
Volume Number: 15
Issue Number: 5
Column Tag: ToolBox Techniques
Tune-up your Application with Icon Services
by Craig Hockenberry, Chief Typist, The Iconfactory
Techniques for adding 32-bit icons to your
application
Introduction
Many new, exciting technologies were introduced in Mac OS 8.5. One of the most
overlooked is the Icon Services API. With the introduction of Icon Services, many
common tasks, such as showing icons for the desktop, have become much simpler.
The architecture of the Icon Services API is based on a data abstraction for icons.
Instead of using handles to icon data (as was the case with Icon Utilities), Icon Services
relies on an icon reference. The icon reference is a truly opaque data type used in many
useful API functions for managing and displaying the iconic data.
In this article, I'll focus on how to implement the new icons within an existing
application framework which is based on handles to icon suites, not icon references.
These techniques were used when the Iconfactory upgraded its popular IconDropper
software to support 32-bit icons (see Figure 1). I'll also show some techniques for
making sure that your icons are Appearance-compliant.
With a small amount of work, you can update your application and make it look much
better with Mac OS 8.5 and beyond.
Figure 1. The IconDropper interface using 32-bit icons.
Why use Icon Services?
Icon Services provides the developer with a host of new features. The following are
some of the highlights:
The icons look better! 32-bits rock!
The most obvious benefit to using Icon Services is its capability to display 32-bit
icons. The full RGB color space makes smooth gradients and realistic looking shadows a
snap to design. The new icon format also allows an 8-bit mask for varying levels of
transparency.
That's the technical explanation. It's also OK to just say that the new icons look cool and
are much easier on the eye, as shown in Figure 2.
Figure 2. Example of 256 color icon (left) and updated version in 32-bits (right)
Consistency of icons throughout all applications
A less obvious benefit of Icon Services is its ability to deal with the icon as an opaque
data type. This allows Icon Services to cache the icon data across all applications
(including the Finder.)
In previous incarnations of the Mac OS, the Finder maintained its own cache of icons
from the desktop database. Other applications did not have access to this cache, so many
developers had to implement their own schemes for keeping track of desktop icons.
The cache managed by Icon Services is completely accessible by your application. And,
best of all, the cache is automatically updated when the user switches to a different
theme or scheme. You don't have to do anything to be Appearance-compliant.
One thing you need to be careful of when dealing with the cache, is managing the
reference counts. I'll cover this in more detail when we get to the example code.
Easier to code, especially with file and folder icons
One of the most tedious tasks in previous versions of the Mac OS was getting the icon
for an item on the desktop. This seemingly simple task involved reading Finder info,
the desktop database and custom icon resources.
With Icon Services, getting the icon is as simple as supplying a type/creator code or a
FSSpec. Hundreds of lines of code are replaced with a few simple calls to the API.
A single icon resource type
Anyone who's dealt with icon suites knows the joy of dealing with the multitude of
handles for each of the icon sizes and depths. Icon Services encapsulates all these icon
types into a single resource, the 'icns' resource. Icon Services uses the term icon
family when referring to this resource.
Dealing with one handle is obviously simpler than dealing with a dozen or more
handles.
Having all the icon data in one resource also has a less obvious benefit: it decreases
network traffic when you are reading the icon resources off a volume that is not local.
A dozen file accesses over a network takes much longer than just one.
Things Aren't Perfect
There are a couple of things to be wary of when dealing with the new icon resources:
BNDL resources are still 256 colors
The bundle resource has not been changed in Mac OS 8.5. Changing the BNDL would
cause previous versions of the Finder to crash when mounting a volume with the new
icons.
So what does this have to do with Icon Services? It means that your application and
document icons will still be restricted to 256 colors and 1 bit masks. The new 32-bit
icon format is not supported in the desktop database.
Copy and paste compatibility problems
As developers, we sometimes use custom icon resources to "pretty up" Read Me files
and other files in a distribution. Be aware that copying and pasting an icon in Mac OS
8.5 will only copy the new icon resource ('icns') and not the older resources. The
result is that a user on an older version of the OS will not see the custom icon.
Appearance Manager can only handle icon suites
The new Appearance Manager only allows icon suites in the new control resource
parameters. Unfortunately, there is no way to specify an icon family for your beveled
buttons or icon panes, so some resource conversions are required.
I'll present solutions to these problems later on in this article.
Examples
Now that we've gotten the boring bits out of the way, let's get down to the interesting
stuff: code to do icons.
Useful header files and libraries
Before getting started with Icon Services, there are a few things that you should setup
in your development environment.
To use Icon Services, new header files and a stub library are required. You'll find
everything you need in the Universal Interfaces 3.2, which can be downloaded from
Apple's website (http://developer.apple.com/sdk).
All of the new definitions and declarations for Icon Services are found in "Icons.h". If
you're dealing with custom icons or other Finder information, you'll also find
"Finder.h" useful.
You also need to link in the IconServicesLib stub library. Again, this is found in the
Universal Interfaces 3.2 distribution. Make sure that the library is linked weak or
you'll get errors when running on a system before Mac OS 8.5. In CodeWarrior, you
set this attribute by setting "Import Weak" in the project inspector.
Note that Icon Services is only supported on Mac OS 8.5, so you should only link the
library in your PPC target. I'll discuss some strategies for dealing with older versions
of the Mac OS later on in this article.
Checking Gestalt
Before calling any of the entry points in Icon Services, you should make sure that it's
supported. This is simple to do with a call to Gestalt:
// check if Icon Services is available
Int32 gestaltResult;
OSErr osErr = Gestalt(gestaltIconUtilitiesAttr,
gestaltResult);
if (osErr != noErr)
{
useIconServices = false;
}
else
{
useIconServices = (gestaltResult &
(1L << gestaltIconUtilitiesHasIconServices)) != 0;
}
If Icon Services is not available, you can fall back on Icon Utilities for managing and
displaying icons.
Plotting a file or folder's icon
Let's start off with a simple example to display an icon for an item using its file
specification.
The first thing to do is get the icon reference for the item:
FSSpec theItem;
// set theItem to a file or folder you want to display
IconRef theIconRef;
SInt16 theLabel;
osErr = GetIconRefFromFile(&theItem, &theIconRef, &theLabel);
Now, let's plot the icon reference:
Rect theRect;
// set theRect to the drawing coordinates
IconAlignmentType theAlignment = kAlignNone;
IconTransformType theTransform = theLabel;
osErr = PlotIconRef(&theRect, theAlignment, theTransform,
kIconServicesNormalUsage, theIconRef);
That's all you need to plot the icon. But there's one last step that's very important; you
need to release the icon reference:
osErr = ReleaseIconRef(theIconRef);
This final step is important because it decrements a usage counter for the Icon
Services cache. When this use count reaches zero, the memory for the item in the
cache is released.
You don't have to worry about disposing of the icon resources yourself, but if you
forget to release the icon reference, the usage count will never reach zero and you'll
end up with a memory leak.
Updating Existing Applications
One of the challenges I was faced with when upgrading our applications to Icon Services
was backward compatibility. Since Icon Services is only supported on Mac OS 8.5, I
needed to find a way to handle the icon data with both old and new versions of the OS.
Luckily, there are some simple ways to handle the icon data in both old and new
formats.
Resource ids in existing code
In some cases, your application will reference the icon by its resource ID. When this
is the case, you only need to change the way in which that resource ID is drawn.
The following code (Listing 1) is a snippet from a modified version of PowerPlant's
LIconPane class. The DrawSelf member function has been modified to plot a 32-bit
icon if Icon Services is available. If Icon Services is not available, the PlotIconID
function from Icon Utilities is used to display the icon.
Listing 1: CIconPane::DrawSelf()
CIconPane::DrawSelf()
Plot 32-bit icon using resource id if Icon Services is available, else
use Icon Utilties.
void
CIconPane::DrawSelf()
{
OSErr osErr;

Rect frame;
CalcLocalFrameRect(frame);
// check if Icon Services is available
bool useIconServices;
Int32 gestaltResult;
osErr = Gestalt(gestaltIconUtilitiesAttr, &gestaltResult);
if (osErr != noErr)
{
useIconServices = false;
}
else
{
useIconServices = (gestaltResult &
(1L << gestaltIconUtilitiesHasIconServices)) != 0;
}
// get the file spec for the current process if necessary
FSSpec appSpec;
if (useIconServices)
{
ProcessSerialNumber appPSN;
ProcessInfoRec appPIR;

appPSN.highLongOfPSN = 0;
appPSN.lowLongOfPSN = kCurrentProcess;

appPIR.processInfoLength = sizeof(ProcessInfoRec);
appPIR.processName = nil;
appPIR.processAppSpec = &appSpec;

osErr = ::GetProcessInformation(&appPSN, &appPIR);
if (osErr != noErr)
{
useIconServices = false;
}
}

if (useIconServices)
{
// plot the icon with Icon Services
IconRef iconRef;
::RegisterIconRefFromResource('Icnp', 'TEMP', &appSpec,
mIconID, &iconRef);
::PlotIconRef(&frame, kAlignNone, kTransformNone,
kIconServicesNormalUsageFlag, iconRef);
::ReleaseIconRef(iconRef);
::UnregisterIconRef('Icnp', 'TEMP');
}
else
{
// plot the icon with Icon Utilities
::PlotIconID(&frame, kAlignNone, kTransformNone,
mIconID);
}
}
Note that the behavior of this class is a little different than the original LIconPane.
CIconPane will only search the application's resource fork (via the appSpec).
PlotIconID, on the other hand, will search through the resource chain as it looks for
mIconID.
It's also important to note the first two parameters of
RegisterIconRefFromResource(), the creator and type. Typically, these will be
derived from your application's creator and types. In the above example, I've used a
creator code that matches the PowerPlant class identifier and a type of 'TEMP'. This
was done so that the class could be used in any project. Be aware that this method will
only work for "temporary" icon references since the type and creator are the primary
indexing method for the icon cache.
The full source code for this PowerPlant class is available for download from the
MacTech FTP site. The demonstration project also includes other classes which show
various features of Icon Services.
Don't forget the resources!
As with all these strategies for backward compatibility, you need to make sure that you
include resource data in both old and new formats.
For older systems, you'll be including 'ICN#', 'icl8', etc. For the 32-bit icons, you'll
need to add the 'icns' resource type (with the same resource ID as the corresponding
ICN# data).
I'll talk a little about creating these new resource types at the end of this article.
Handles to icons suites in existing code
The example above works well for "static" icon data. But an approach based on
resource ids will not work for icons that are dynamic (in a Finder-like list view, for
example). In this case, you have to maintain a reference to the icon data in the object